Skip to content

[feat] roadmap 14 batch CN - Connect UX: state machine, disconnect, info drawer, invite ~srv - #40

Merged
AlexZ005 merged 2 commits into
mainfrom
feature/roadmap-14-cn-connect-ux
Jul 24, 2026
Merged

[feat] roadmap 14 batch CN - Connect UX: state machine, disconnect, info drawer, invite ~srv#40
AlexZ005 merged 2 commits into
mainfrom
feature/roadmap-14-cn-connect-ux

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

Roadmap #14 Batch CN — Connect UX (plan of record: cloud repo docs/plans-core/roadmap-14-connect-ux-cloud.md).

The pill is now a state machine

State Pill contents
idle 📋 invite copy · dial input + blue Connect · (i)
pending disabled input (dialed id) · amber Cancel · pulsing "Waiting for approval…" (aria-live)
connected 🟢 Connected · <host> [+N] · red Disconnect · (i) — no dial input

State derives from opened connections (openedPeers), never userdata.length — the roster fills optimistically at dial time (the phantom-peer trap; the Users peers-trigger had the same bug and is fixed in this PR too). data-state on .connect-pill for e2e.

  • sessionHost (new store-only connectionState.js): set when our outbound request is approved; null = you're hosting. Cleared on host disconnect / cancel / leave.
  • Cancel (peerApproval.cancelOutboundRequest): unwinds the queue entry, conn (deleted before close so the onConnClose stale-guard and the 4s restore-retry both stand down), whitelist, and host bookkeeping. Also added to the outbound-pending toast.
  • Disconnect (PeerConnection.leaveSession()): closes every conn with explicit full teardown (close events are often never signaled — the P-A finding), resets the roster to self, clears both approval queues. No peer.destroy() — your invite id stays valid. Local scene kept.

(i) info drawer (replaces the always-on server indicator)

Session (state badge · own id · host · per-peer RTT/relay + joined-ago · inline cancels) / Server (resolved server · fallback warning · measured ping to the peerjs info endpoint · discovery probe) / a new drawerSlot cloud mount (room/host settings land here in batch RM). The (i) gains an amber badge when didFallback. Outside-close uses a window pointerdown listener — a fixed catcher would size to the pill, not the viewport (.connect-wrap's translateX is the containing block for fixed descendants; the CLAUDE.md transform gotcha).

Invite links pin the signaling world (~srv)

#A1B2C~srv=public / ~srv=<encoded host[:port][path]> — appended by copy only when the resolved server differs from a fresh default build (fallback / explicit public / custom). Parsed at the top of the PeerConnection constructor (the peer.on('open') hash flow runs too late for server selection); the override is consulted first in resolvePeerOptions/describePeerServer, session-only, and never falls back. Only the ~srv tail is stripped, so hash auto-connect is unchanged.

Latent bug fixed

Dialing while the signaling link was down threw inside peer.connect() (undefined conn → conn.on TypeError) and silently stranded the request. Now guarded in Connect + connectToPeer + restoreConnection with clear toasts.

Verification

  • connect-states.test.cjs (new) — 19/19 PASS: idle→pending→Cancel full store unwind, no retry resurrection after cancel, ~srv encode/parse/decode round-trips, copied link carries the param. Two-peer connected/Disconnect block env-gated (TWO_PEER=1 + the hosts mapping).
  • roadmap-13-ui-polish — I5 block migrated to the drawer (label, fallback flip, warning row, (i) badge, outside-close) — ALL PASS.
  • Verified state visuals via screenshots (pending drawer + connected drawer).
  • svelte-check 498/76 (below the 499/77 baseline) · npm run build green.

🤖 Generated with Claude Code

AlexZ005 and others added 2 commits July 24, 2026 10:02
…nfo drawer, invite ~srv

The Connect pill becomes a three-state machine with professional session controls,
the always-on server indicator moves into an (i) info drawer, and invite links can
pin the signaling world.

- connectionState.js (NEW, store-only): sessionHost (set when OUR outbound request
  is approved; null = hosting), peerJoinedAt, resetSession. Cleared per-peer in
  handleDisconnected.
- Connect.svelte: state derives from OPENED peers (never userdata.length - the
  roster fills at dial time). idle = dial input + blue Connect + (i); pending =
  disabled input + AMBER Cancel + pulsing "Waiting for approval..." (aria-live);
  connected = NO input, green dot + "Connected · <host> [+N]" + RED Disconnect.
  data-state attr for e2e. copy() appends the invite ~srv param and guards the
  "Generating..." pre-id state.
- peerApproval.cancelOutboundRequest: unwind a pending dial (queue entry, conn -
  deleted BEFORE close so the onConnClose stale-guard and the restore retry loop
  both stand down - whitelist, sessionHost). Also wired into the outbound-pending
  toast (Cancel button).
- PeerConnection.leaveSession(): close every conn with EXPLICIT full teardown
  (handleDisconnected + env-presets/hand-model drops - close events are often
  never signaled), roster reset to self, queues cleared; NO peer.destroy() so the
  invite id stays valid.
- ConnectInfoDrawer.svelte (NEW): Session (state badge, own id, host, per-peer
  rtt/relay via the now-exported qColor + joined-ago, inline cancel) / Server
  (resolved server, fallback warning, measured ping via the peerjs info endpoint,
  discovery probe 200/401) / cloud drawerSlot mount (rooms settings land here in
  batch RM). Outside-close via a WINDOW pointerdown listener - a fixed catcher
  would size to the pill, not the viewport (.connect-wrap's translateX is the
  containing block for fixed descendants; the CLAUDE.md transform gotcha).
- Invite ~srv (peerServer.js): inviteServerParam (empty for default resolution;
  ~srv=public on fallback/explicit-public-with-self-hosted; encoded host:port/path
  for custom) + parseInviteHash/decodeInviteServer + applyInviteServerOverride
  consulted FIRST in resolvePeerOptions/describePeerServer (session-only, never
  falls back). Parsed at the TOP of the PeerConnection constructor (the
  peer.on('open') hash flow runs too late for server selection); only the ~srv
  tail is stripped so hash auto-connect is unchanged.
- FIX (latent): dialing while the signaling link is down threw inside
  peer.connect() (undefined conn) and silently stranded the request - now guarded
  in Connect + connectToPeer + restoreConnection with clear toasts.
- FIX: the Users peers-trigger showed a phantom peer while an outbound request was
  pending (userdata gate) - now also gated on openedPeers.
- e2e: ui-polish I5 block migrated to the drawer (+ fallback badge/warning/outside
  -close checks); NEW connect-states suite (19 checks: idle->pending->Cancel full
  store unwind, no retry resurrection, ~srv encode/parse/decode round-trips,
  copied link carries the param; two-peer connected/Disconnect block env-gated via
  TWO_PEER=1).

svelte-check 498/76 (below the 499/77 baseline), build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the round (i) info button with a chevron disclosure that rotates 180deg on
open, and make the info panel SLIDE down from under the pill instead of popping in:
- Connect.svelte: cx-toggle chevron button (fa-chevron-down, rotates via .open),
  aria-expanded; keeps the id/testids + the amber fallback badge.
- ConnectInfoDrawer.svelte: transition:slide (200ms cubicOut) on the panel — the
  translateX centering is preserved (slide animates height, not transform).
- e2e: the outside-close check now polls past the slide-out transition (eventually).

svelte-check 498/76, build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AlexZ005
AlexZ005 merged commit 8eb5bdc into main Jul 24, 2026
@AlexZ005
AlexZ005 deleted the feature/roadmap-14-cn-connect-ux branch August 5, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant